Skip to content

React Server Components

Video Summary

Here's some PHP code:

<?php
$siteTitle = 'Hello World!';
$link = mysqli_connect("localhost", "root", "passw0rd");
$data = mysqli_query($link, "SELECT * FROM products");
?>
<!DOCTYPE html>
<html>
<head>
<title><?php echo $siteTitle ?></title>
</head>
<body>
<h1>Trending Products</h1>
<?php
while ($item = mysqli_fetch_array($data)) {
echo "<article>";
echo "<h2>" . $item['title'] . "</h2>";
echo "<p>" . $item['description'] . "</p>";
echo "</article>";
}
?>
</body>
</html>

PHP is a server-rendered language, and it's actually really cool. PHP is a superset of HTML; every HTML document is a valid PHP document. PHP has one additional super-power, though: <?php> tags.

When the user visits this page, the server evaluates all of these <?php> tags to produce the static HTML file that will be sent to the user. In this particular case, we're connecting to a database, fetching all of the products, and then mapping over them. The final result would look something like this:

<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Trending Products</h1>
<article>
<h2>Toothbrush</h2>
<p>A very important tool</p>
</article>
<article>
<h2>Screwdriver</h2>
<p>Also a very important tool!</p>
</article>
</body>
</html>

I started writing code like this well over a decade ago, and it still seems really cool to me. It's incredibly powerful!

The downside is that the PHP only runs on the server. If we want to make this page interactive, to allow users to rearrange the <article> elements, or add/remove items, we need to do that in JavaScript, a completely different language in a totally different place. As you might imagine, this gets pretty messy.

Let's compare this PHP approach to how we've solved this sort of thing so far in this course:

import useSWR from 'swr';
async function fetcher(endpoint) {
const response = await fetch(endpoint);
const json = await response.json();
return json;
}
function TrendingProducts() {
// data: null, isLoading: true
const { data, isLoading } = useSWR(
'/api/get-trending-products',
fetcher
);
if (isLoading) {
return <p>Loading…</p>;
}
return (
<>
<h1>Trending Products</h1>
{data.map((item) => (
<article key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</article>
))}
</>
);
}
export default TrendingProducts;

Using the SWR library, we make a network request to /api/get-trending-products. This request will take some time to complete, and so we add a loading state.

We have to do it this way because we can't make database requests directly in the browser. So, we need to move the database-querying logic to the server, and fire off a network request to the server to do this work for us.

(It might seem bad that the PHP version doesn't have a loading state: doesn't that mean the user will be staring at a blank white screen while the database query runs? This is true, but database queries themselves tend to be pretty quick; the slow part is waiting for that data to be sent over a network. Often, the database runs on the same machine as the web server, and so we can do those queries very quickly.)

So, the SWR version is both more complicated, and likely a worse user experience. But we don't have a choice when building client-side React apps!

Fortunately, things improve in a full-stack context, when we can run some JavaScript code on the server. Next.js was first released in 2016, and here's how they opted to solve this problem:

function Homepage({ data }) {
return (
<>
<h1>Trending Products</h1>
{data.map((item) => (
<article key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</article>
))}
</>
);
}
export async function getServerSideProps() {
const link = mysqliConnect('localhost', 'root', 'passw0rd');
const data = await mysqliQuery(link, 'SELECT * FROM products');
return {
props: {
data,
},
};
}
export default Homepage;

getServerSideProps is a special function. When the page is visited, Next will invoke this function. We return a props object, and those props will be passed into the component for the initial server render.

This is very cool, but there are 3 downsides:

  1. This is not an "official" part of React, it's a Next feature. Other full-stack frameworks like Gatsby or Remix came up with their own conventions. This is not “standardized”.
  2. We can't do this in any React component, only the ones at the very top of the tree (eg. we can do it in Homepage, but not LoginForm).
  3. The React component will always hydrate on the client, even when there's no need for it to do so (we'll dig into this point later).

For the past few years, the React team has been quietly working on a first-party solution that addresses these problems. The solution they've come up with is called React Server Components (RSC for short).

Here's what the code looks like:

import React from 'react';
async function TrendingProducts() {
const link = mysqliConnect('localhost', 'root', 'passw0rd');
const data = await mysqliQuery(link, 'SELECT * FROM products');
return (
<>
<h1>Trending Products</h1>
{data.map((item) => (
<article key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</article>
))}
</>
);
}
export default TrendingProducts;

Almost right away, things are pretty funky here. TrendingProducts is a React component, but it's also an async function?? What's going on here?

The big idea with “Server Components” is that they only run on the server. It's quite a bit like the PHP code we saw earlier!

More technically, Server Components are never hydrated on the client. They're intended to be used for components that don't need to be interactive.

There's been a lot of confusion online about React Server Components. I found this stuff pretty confusing too. Once I started playing with them, though, it started to make sense pretty quickly.

Let's clear up some of the most common sources of confusion.

The first thing to understand is that “React Server Components” is an optional new “mode” for React. It's a bit like <StrictMode>. React Server Components is a new paradigm, which can be implemented for a specific application.

Within this optional “mode”, there are two types of components: Server Components and Client Components.

(Yes, it is confusing that the mode is called React Server Components, and within that mode, there are “Server Components”. Naming things is hard!)

Server components only render on the server. As a result, they can't create new state variables (they wouldn't be able to re-render if the state changed!). They also can't consume context, or use effects. They're a small subset of a typical React component.

Client Components are the components we've been working with throughout this course. Every component we've looked at so far is a client component.

Confusingly, Client Components still render on the server during Server Side Rendering. They're called “Client Components” because they do render on the client, unlike “Server Components”:

Another common source of confusion is how React Server Components (RSC) fits together with Server Side Rendering (SSR). Is RSC a replacement for SSR? Do we have to pick one or the other??

RSC and SSR are separate features. It's possible to use them separately. But they're intended to be used together:

In the React Server Components paradigm, we need to declare whether a component is a Server Component or a client component. We can do this with directives:

'use client';
import React from 'react';
function Counter() {
const [count, setCount] = React.useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
export default Counter;

If you haven't seen this sort of thing before, it looks super funky. Why is there a random string at the start of our program?

This convention started with 'use strict', a directive that tightened some of the rules in JavaScript. The React team has re-used this convention.

Server Components don’t need to be given a directive; all components are assumed to be Server Components by default.

So far, the only framework to fully adopt React Server Components is Next. In Next, all components are assumed to be Server Components by default, and so we can omit the 'use server' directive. We only need to specify it for Client Components.

I know that this stuff can feel super overwhelming at first, but don't worry! I promise this stuff will start to click once we start experimenting with them. The hands-on practice is coming soon.

Two corrections for the above video:

  • I mention in the video that the "use server" directive could theoretically be used to declare a Server Component, but that’s not true; "use server" is used for a completely separate purpose, part of the new Actions API. We don’t yet cover this API in the course, but you can read about it in the React docs (opens in new tab).
  • I show some functions for connecting to a MySQL database, mysqliConnect and mysqliQuery. These functions aren't real; they're placeholders meant to make it easier to compare the PHP and JS versions.
    • If you're interested in working with an SQL database in JavaScript, I'd use PostgreSQL instead of MySQL. The NPM package pg (opens in new tab) appears to be the most popular option for working with PostgreSQL in JS.

The benefits of React Server Components

As we saw above, React Server Components are a significant paradigm shift, an entirely new way to think about components!

Why go through all the trouble? What are the benefits?

The most obvious benefit to me is performance. If half of the components in your application are Server Components, it means that your JS bundle will be significantly smaller, since all of that code can be omitted.

We're already seeing some innovation happening here: Bright (opens in new tab) is a syntax-highlighting library specifically designed to be used with React Server Components.

Screenshot of the Bright homepage, showing a syntax-highlighted code snippet

If we were to try to use this library in a typical React app, it would add a whopping 250kb gzip to our JavaScript bundle. Syntax-highlighting libraries tend to be quite large, since they have to include complex tokenization logic for potentially dozens of languages. Generally, these libraries have to make lots of trade-offs, in order to shrink them to an acceptable size.

But, Bright doesn't get added to your JS bundle! It runs on the server, generating all of the HTML needed to display syntax-highlighted code. Server components don't hydrate or re-render on the client, and so we don't need this code to be shipped to the browser.

This, to me, is the most obvious benefit to React Server Components. When I've spoken to members of the React core team, however, they've said that it isn't yet clear what the biggest benefits will be. It's a whole new paradigm, and it remains to be seen what benefits are made possible by it.

Understanding with graphs

Let's see how each of these approaches affect the graphs we've been looking at. Toggle between the different choices to see how things change:

Server Side Rendering

This is a data visualization which shows a sequence of events between client and server. Each event is represented here as a list item.

  1. "Query Data" on server. Duration: 4 units of time.
  2. "Render App" on server. Duration: 4 units of time.
  3. "Render App" on server. Duration: 4 units of time.
  4. Response from server. Duration: 4 units of time.
  5. "Download JS" on client. Duration: 10 units of time.
  6. "Hydrate" on client. Duration: 4 units of time.